package edu.northwestern.cbits.purple_robot_manager.db;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.MatrixCursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import edu.northwestern.cbits.purple_robot_manager.db.filters.Filter;
import edu.northwestern.cbits.purple_robot_manager.db.filters.FrequencyThrottleFilter;
import edu.northwestern.cbits.purple_robot_manager.db.filters.ValueDeltaFilter;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.probes.builtin.AccelerometerProbe;
import edu.northwestern.cbits.purple_robot_manager.probes.builtin.GyroscopeProbe;
import edu.northwestern.cbits.purple_robot_manager.probes.builtin.LightProbe;
import edu.northwestern.cbits.purple_robot_manager.probes.builtin.MagneticFieldProbe;
import edu.northwestern.cbits.purple_robot_manager.probes.builtin.PressureProbe;
public class ProbeValuesProvider
{
public static final String INTEGER_TYPE = "integer";
public static final String REAL_TYPE = "real";
public static final String TEXT_TYPE = "text";
public static final String TIMESTAMP = "timestamp";
private static final String ID = "_id";
private Context _context = null;
protected static final int FIELD_TYPE_UNKNOWN = -1;
protected static final int FIELD_TYPE_NULL = 0;
protected static final int FIELD_TYPE_INTEGER = 1;
protected static final int FIELD_TYPE_FLOAT = 2;
protected static final int FIELD_TYPE_STRING = 3;
protected static final int FIELD_TYPE_BLOB = 4;
private ArrayList<Filter> _filters = new ArrayList<>();
private long _lastCleanup = 0;
private static ProbeValuesProvider _instance = null;
private HashMap<String, Long> _lastUpdates = new HashMap<>();
public static ProbeValuesProvider getProvider(Context context)
{
if (ProbeValuesProvider._instance == null)
ProbeValuesProvider._instance = new ProbeValuesProvider(context.getApplicationContext());
return ProbeValuesProvider._instance;
}
public ProbeValuesProvider(Context context)
{
this._context = context.getApplicationContext();
HashSet<String> highFreq = new HashSet<>();
highFreq.add(AccelerometerProbe.DB_TABLE);
highFreq.add(GyroscopeProbe.DB_TABLE);
highFreq.add(MagneticFieldProbe.DB_TABLE);
this._filters.add(new FrequencyThrottleFilter(1000, null, highFreq)); // Don't
// save
// any
// readings
// at
// a
// larger
// than
// 1
// second
// interval
// for
// most
// sensors...
this._filters.add(new FrequencyThrottleFilter(100, highFreq, null)); // Only
// save
// high-frequency
// data
// at
// 0.1s
// intervals...
// Proximity: Identical values
HashSet<String> quarterDelta = new HashSet<>();
quarterDelta.add(AccelerometerProbe.DB_TABLE);
quarterDelta.add(PressureProbe.DB_TABLE);
quarterDelta.add(GyroscopeProbe.DB_TABLE);
this._filters.add(new ValueDeltaFilter(0.25, quarterDelta));
HashSet<String> fullDelta = new HashSet<>();
fullDelta.add(MagneticFieldProbe.DB_TABLE);
this._filters.add(new ValueDeltaFilter(1.0, fullDelta));
HashSet<String> fiveDelta = new HashSet<>();
fiveDelta.add(LightProbe.DB_TABLE);
this._filters.add(new ValueDeltaFilter(5.0, fiveDelta));
}
public void close()
{
// this._dbHelper.close();
}
private String tableName(Context context, String name, Map<String, String> schema)
{
String tableName = name;
ArrayList<String> columns = new ArrayList<>(schema.keySet());
Collections.sort(columns);
for (String key : columns)
{
tableName += (key + schema.get(key));
}
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(tableName.getBytes("UTF-8"));
tableName = "table_" + (new BigInteger(1, digest)).toString(16);
}
catch (NoSuchAlgorithmException | UnsupportedEncodingException e)
{
LogManager.getInstance(context).logException(e);
}
return tableName;
}
private boolean tableExists(Context context, String tableName)
{
Cursor c = null;
boolean tableExists = false;
ProbeValuesSqlHelper helper = new ProbeValuesSqlHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
try
{
c = database.query(tableName, null, null, null, null, null, null);
tableExists = true;
}
catch (SQLiteException e)
{
// LogManager.getInstance(context).logException(e);
}
finally
{
if (c != null)
c.close();
}
database.close();
return tableExists;
}
private boolean isValidColumn(String key)
{
// TODO: Add more checks...
return true;
}
private boolean createTable(String name, Map<String, String> schema)
{
String createSql = "create table " + name + " (" + ProbeValuesProvider.ID
+ " integer primary key autoincrement";
createSql += (", " + ProbeValuesProvider.TIMESTAMP + " real");
for (String key : schema.keySet())
{
if (this.isValidColumn(key))
{
String dbType = null;
String type = schema.get(key);
if (ProbeValuesProvider.REAL_TYPE.equals(type))
dbType = ProbeValuesProvider.REAL_TYPE;
else if (ProbeValuesProvider.INTEGER_TYPE.equals(type))
dbType = ProbeValuesProvider.INTEGER_TYPE;
if (ProbeValuesProvider.TEXT_TYPE.equals(type))
dbType = ProbeValuesProvider.TEXT_TYPE;
if (dbType != null)
createSql += (", " + key + " " + dbType);
}
}
createSql += ");";
ProbeValuesSqlHelper helper = new ProbeValuesSqlHelper(this._context);
SQLiteDatabase database = helper.getWritableDatabase();
database.execSQL(createSql);
database.close();
return true;
}
public void insertValue(final Context context, final String name, final Map<String, String> schema,
final Map<String, Object> values)
{
long now = System.currentTimeMillis();
long lastUpdate = 0;
if (this._lastUpdates.containsKey(name))
lastUpdate = this._lastUpdates.get(name);
if (now - lastUpdate < 5000)
return;
this._lastUpdates.put(name, now);
final ProbeValuesProvider me = this;
final HashSet<Long> attempts = new HashSet<>();
Runnable r = new Runnable()
{
public void run()
{
try
{
ProbeValuesSqlHelper helper = new ProbeValuesSqlHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
synchronized (database) {
for (Filter f : me._filters) {
if (f.allow(name, values) == false)
return;
}
long now = System.currentTimeMillis();
if (now - me._lastCleanup > 300000) // Flush old entries
me.cleanup(context);
String localName = me.tableName(context, name, schema);
if (me.tableExists(context, localName) == false)
me.createTable(localName, schema);
ContentValues toInsert = new ContentValues();
for (String key : schema.keySet()) {
String type = schema.get(key);
if (ProbeValuesProvider.REAL_TYPE.equals(type)) {
try {
Double d = (Double) values.get(key);
toInsert.put(key, d);
} catch (ClassCastException e) {
try {
Float f = (Float) values.get(key);
toInsert.put(key, f.doubleValue());
} catch (ClassCastException ee) {
Integer i = (Integer) values.get(key);
toInsert.put(key, i.doubleValue());
}
}
} else if (ProbeValuesProvider.INTEGER_TYPE.equals(type)) {
Integer i = (Integer) values.get(key);
toInsert.put(key, i);
} else if (ProbeValuesProvider.TEXT_TYPE.equals(type))
toInsert.put(key, values.get(key).toString());
}
toInsert.put(ProbeValuesProvider.TIMESTAMP, (Double) values.get(ProbeValuesProvider.TIMESTAMP));
database.insert(localName, null, toInsert);
}
database.close();
}
catch (SQLiteException e)
{
try {
Thread.sleep(250);
} catch (InterruptedException e1) {
}
attempts.add(System.currentTimeMillis());
if (attempts.size() < 10)
this.run();
}
}
};
try
{
Thread t = new Thread(r);
t.start();
}
catch (OutOfMemoryError e)
{
LogManager.getInstance(context).logException(e);
}
}
private void cleanup(Context context)
{
try
{
this._lastCleanup = System.currentTimeMillis();
String tableSelect = "select name from sqlite_master where type='table';";
ProbeValuesSqlHelper helper = new ProbeValuesSqlHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
Cursor c = database.rawQuery(tableSelect, null);
while (c.moveToNext())
{
String tableName = c.getString(c.getColumnIndex("name"));
try
{
if (tableName.startsWith("table_"))
{
Cursor cursor = database.query(tableName, null, null, null, null, null, null);
cursor.close();
SQLiteStatement delete = database.compileStatement("delete from " + tableName + " where "
+ ProbeValuesProvider.ID + " not in (select " + ProbeValuesProvider.ID + " from "
+ tableName + " order by " + ProbeValuesProvider.TIMESTAMP + " desc limit 2048);");
delete.execute();
cursor = database.query(tableName, null, null, null, null, null, null);
cursor.close();
}
}
catch (SQLException e)
{
LogManager.getInstance(context).logException(e);
}
}
database.close();
c.close();
}
catch (RuntimeException e)
{
LogManager.getInstance(context).logException(e);
}
}
public void clear(Context context)
{
ProbeValuesSqlHelper helper = new ProbeValuesSqlHelper(context);
String tableSelect = "select name from sqlite_master where type='table';";
SQLiteDatabase database = helper.getWritableDatabase();
Cursor c = database.rawQuery(tableSelect, null);
ArrayList<String> names = new ArrayList<>();
while (c.moveToNext())
names.add(c.getString(c.getColumnIndex("name")));
c.close();
for (String name : names)
{
try
{
SQLiteStatement delete = database.compileStatement("delete from " + name + " where (_id != -1)");
delete.execute();
}
catch (SQLException e)
{
LogManager.getInstance(context).logException(e);
}
}
database.close();
}
static int getType(Cursor cursor, int i) throws Exception
{
SQLiteCursor sqLiteCursor = (SQLiteCursor) cursor;
CursorWindow cursorWindow = sqLiteCursor.getWindow();
int pos = cursor.getPosition();
if (cursorWindow.isNull(pos, i))
return ProbeValuesProvider.FIELD_TYPE_NULL;
else if (cursorWindow.isLong(pos, i))
return ProbeValuesProvider.FIELD_TYPE_INTEGER;
else if (cursorWindow.isFloat(pos, i))
return ProbeValuesProvider.FIELD_TYPE_FLOAT;
else if (cursorWindow.isString(pos, i))
return ProbeValuesProvider.FIELD_TYPE_STRING;
else if (cursorWindow.isBlob(pos, i))
return ProbeValuesProvider.FIELD_TYPE_BLOB;
return ProbeValuesProvider.FIELD_TYPE_UNKNOWN;
}
public Cursor retrieveValues(Context context, String name, Map<String, String> schema)
{
MatrixCursor matrix = null;
ProbeValuesSqlHelper helper = new ProbeValuesSqlHelper(context);
synchronized (this)
{
SQLiteDatabase database = helper.getWritableDatabase();
String localName = this.tableName(context, name, schema);
if (this.tableExists(context, localName) == false)
this.createTable(localName, schema);
try
{
Cursor c = database.query(localName, null, null, null, null, null, ProbeValuesProvider.TIMESTAMP);
matrix = new MatrixCursor(c.getColumnNames());
while (c.moveToNext())
{
Object[] values = new Object[c.getColumnCount()];
for (int i = 0; i < c.getColumnCount(); i++)
{
switch(ProbeValuesProvider.getType(c, i))
{
case ProbeValuesProvider.FIELD_TYPE_BLOB:
values[i] = c.getBlob(i);
break;
case ProbeValuesProvider.FIELD_TYPE_INTEGER:
values[i] = c.getInt(i);
break;
case ProbeValuesProvider.FIELD_TYPE_FLOAT:
values[i] = c.getFloat(i);
break;
case ProbeValuesProvider.FIELD_TYPE_STRING:
values[i] = c.getString(i);
break;
case ProbeValuesProvider.FIELD_TYPE_NULL:
values[i] = null;
break;
}
}
matrix.addRow(values);
}
c.close();
}
catch (Exception e)
{
LogManager.getInstance(context).logException(e);
}
database.close();
}
return matrix;
}
}